﻿// ////////////////////////////////////////////////////////////////////////////
// 
// SWE Case Tool
// 
// Solution:	CaseTool
// Project:		1. CaseTool
// Filename:	MethodFactory.cs
// 
// File Created:	21:15 30.05.2013 
// Last Changed:	18:11 05.06.2013
// 
// ////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using SWE.CaseTool.GUI;
using SWE.CaseTool.Utils;

namespace SWE.CaseTool
{
	/// <summary>
	/// The method factory searches for suitable methods and their guis. It creates and destroys the
	/// methods.
	/// The methods must be a public class in a assemlby in the given method folder.
	/// </summary>
	public class MethodFactory
	{
		private readonly Dictionary<string, Type> _methods = new Dictionary<string, Type>();
		private readonly Dictionary<Type, Type> _guis = new Dictionary<Type, Type>();
		private readonly Project _project;

		/// <summary>
		/// Initializes a new instance of the <see cref="MethodFactory" /> class.
		/// Parses all *.dll's in the given folder for suitable methods.
		/// </summary>
		/// <param name="project">The project on which the methods will work on.</param>
		/// <param name="methodFolder">The method folder wich contains the method assemböies.</param>
		protected MethodFactory(Project project, string methodFolder)
		{
			_project = project;

			if (Directory.Exists(methodFolder))
			{
				// Find all assemblies
				foreach (var method in Directory.GetFiles(methodFolder).Where(x => x.EndsWith(".dll")))
				{
					var ass = Assembly.LoadFrom(method);
					foreach (var type in ass.GetTypes())
					{
						IsMethod(type);
						IsGUI(type);
					}
				}
			}
		}

		/// <summary>
		/// Determines whether the specified type is a GUI for a mehtod. If true it adds the gui to the gui to
		/// the internal method gui list.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <exception cref="System.ArgumentException">Multiple guis with the same base Method!</exception>
		private void IsGUI(Type type)
		{
			if (type.BaseType == typeof (Form))
			{
				var att = type.GetAttribute<MethodGUIAttribute>();
				if (att != null)
				{
					if (_guis.ContainsKey(att.BaseMethod))
					{
						throw new ArgumentException("Multiple guis with the same base Method!");
					}

					_guis.Add(att.BaseMethod, type);
				}
			}
		}

		/// <summary>
		/// Determines whether the specified type is method or not and adds the type to the method list.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <exception cref="System.ArgumentException">Multiple methods with the same identifier found</exception>
		private void IsMethod(Type type)
		{
			if (type.GetInterface("IMethod") != null)
			{
				var atts = type.GetAttribute<MethodNameAttribute>();
				var name = atts != null ? atts.Name : type.Name;

				if (_methods.ContainsKey(name))
				{
					throw new ArgumentException("Multiple methods with the same identifier found");
				}

				_methods.Add(name, type);
			}
		}

		/// <summary>
		/// Returns a list of all methods which were found in the method folder.
		/// </summary>
		/// <returns></returns>
		public List<String> GetMethods() { return _methods.Keys.ToList(); }

		/// <summary>
		/// Returns the Method type with the specified name (name = uniqe method name).
		/// </summary>
		/// <param name="method">The Method name.</param>
		/// <returns>The method with the given name</returns>
		/// <exception cref="System.ArgumentException">Method not found</exception>
		private Type GetMethod(string method)
		{
			if (!_methods.ContainsKey(method))
			{
				throw new ArgumentException("Method not found");
			}
			return _methods[method];
		}

		/// <summary>
		/// Gets the Method description or an empty string, if the Method does not have a description.
		/// </summary>
		/// <param name="method">The Method.</param>
		/// <returns>The Method description or an empty string, if there is no description.</returns>
		public String GetMethodDescription(string method)
		{
			var att = GetMethod(method).GetAttribute<MethodDescriptionAttribute>();
			return att != null ? att.Description : "";
		}

		/// <summary>
		/// Creates the Method with the given name. Searches the constructor and invokes the one with the most
		/// arguments.
		/// </summary>
		/// <param name="name">The method name.</param>
		/// <returns>the newly created instance of the method</returns>
		/// <exception cref="System.ArgumentException">
		/// Unable to create the Method. There was no suitable
		/// Constructor.
		/// </exception>
		public IMethod CreateMethod(string name)
		{
			var method = GetMethod(name);
			ConstructorInfo constructor = null;
			IMethod instance = null;

			// First search for the constructor with the most arguments, if there are enough arguments.
			if (_project.Data != null)
			{
				constructor = method.GetConstructor(new[] {_project.Data.GetType()});
				if (constructor != null)
				{
					try
					{
						instance = (IMethod) constructor.Invoke(new object[] {_project.Data});
					}
					catch (Exception ex)
					{
						throw new InvalidOperationException(
							"Unable to create the Method. Error while creating the mehtod:\n" + ex.Message, ex);
					}
				}
			}

			// if no constructor was found, searhc for one withour parameters.
			if (_project.Data == null || constructor == null)
			{
				constructor = method.GetConstructor(new Type[] {});
				if (constructor != null)
				{
					try
					{
						instance = (IMethod) constructor.Invoke(new object[] {});
					}
					catch
						(Exception ex)
					{
						throw new InvalidOperationException(
							"Unable to create the Method. Error while creating the mehtod:\n" + ex.Message, ex);
					}
				}
			}

			if (constructor == null)
			{
				throw new ArgumentException("Unable to create the Method. There was no suitable Constructor.");
			}

			return instance;
		}

		/// <summary>
		/// Creates the window for the given method and hands the given method over to the newly created method,
		/// if the method inherits from IMethodGUI.
		/// </summary>
		/// <param name="selected">The method name.</param>
		/// <param name="method">The method instance, which should be displayed in the gui.</param>
		/// <returns>the newly created gui.</returns>
		/// <exception cref="System.ArgumentException">
		/// Unable to load the GUI. No suitable constructor for the GUI.
		/// or
		/// There is no GUI for the given mehtod:  + name
		/// </exception>
		public Form CreateWindow(string selected, IMethod method = null)
		{
			var methodType = GetMethod(selected);
			Type wndType;
			if (_guis.TryGetValue(methodType, out wndType))
			{
				var constructor = wndType.GetConstructor(new Type[] {});
				if (constructor == null)
				{
					throw new ArgumentException("Unable to load the GUI. No suitable constructor for the GUI.");
				}

				Form wnd;
				try
				{
					wnd = ((Form) (constructor.Invoke(new object[] {})));
				}
				catch
					(Exception ex)
				{
					throw new InvalidOperationException(
						"Unable to create the Method Window. Error while creating the method window:\n" + ex.Message, ex);
				}

				var methodGUI = wnd as IMethodGUI;
				if (methodGUI != null)
				{
					methodGUI.SetMethod(method);
				}

				return wnd;
			}

			throw new ArgumentException("There is no GUI for the given mehtod: " + selected);
		}
	}
}